feat(angular): angular adapter and utils#368
feat(angular): angular adapter and utils#368riccardoperra wants to merge 20 commits intoTanStack:mainfrom
Conversation
|
a9d3f16 to
3349a19
Compare
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx affected --targets=test:eslint,test:sherif,t... |
❌ Failed | 2m 55s | View ↗ |
☁️ Nx Cloud last updated this comment at 2026-03-11 12:22:30 UTC
More templates
@tanstack/angular-devtools
@tanstack/devtools
@tanstack/devtools-client
@tanstack/devtools-ui
@tanstack/devtools-utils
@tanstack/devtools-vite
@tanstack/devtools-event-bus
@tanstack/devtools-event-client
@tanstack/preact-devtools
@tanstack/react-devtools
@tanstack/solid-devtools
@tanstack/vue-devtools
commit: |
d1cafc8 to
3f27799
Compare
d82d6a2 to
efa3afd
Compare
5abbcba to
e5e5ec1
Compare
|
@AlemTuzlak integration is done. I probably need pipelines so I can check with pkg.pr.new if everything works with some other devtools like table which is not published yet (unfortunately I always have a lot of issues running pnpm link with angular 😄) |
|
@riccardoperra mind just fixing conflicts and we can get this merged |
Add the @tanstack/angular-devtools package with: - TanStackDevtoolsComponent: standalone Angular component that wraps TanStackDevtoolsCore - Plugin system supporting Angular components rendered into devtools containers - Type definitions for Angular-specific plugin and init configuration - Build config using @analogjs/vite-plugin-angular for AOT compilation
- Remove @analogjs/vite-plugin-angular from build (produces empty chunks for library builds). Use plain esbuild via Vite instead - Angular AOT compilation happens at consumer's build time. - Upgrade Angular dev deps from ^19 to ^20 for TypeScript 5.9 compat. - Remove @angular/build dep (only needed by analogjs plugin). - Add @tanstack/angular-devtools workspace override to root.
- Fix array-type ESLint rule in angular-devtools - Fix import/consistent-type-specifier-style in devtools-utils angular - Sort peerDependencies alphabetically (sherif) - Remove unused devDependencies flagged by knip - Remove angularCompilerOptions from tsconfig (not using ngc)
Since angular-devtools already run the code within afterNextRender in browser env, we don't like to wrap the panel returned from utils
c2ffbb9 to
084d4b1
Compare
084d4b1 to
c52c310
Compare
|
@AlemTuzlak done! |
|
Seems some stuff is failing |
e62e4f7 to
c728cfe
Compare
|
@AlemTuzlak fixed, i tested in my local env and all 28 tasks succeded Anyway, I noticed while testing my examples that warning. I saw "sideEffects" false in core but the generated dist contains some
|
📝 WalkthroughWalkthroughThis pull request introduces comprehensive Angular support to TanStack Devtools, adding documentation, example projects, and a new Changes
Sequence Diagram(s)sequenceDiagram
participant App as Angular App
participant Provider as provideTanStackDevtools
participant Core as TanStackDevtools Component
participant Plugins as Plugin System
participant EventBus as EventClient
participant Renderer as Component Renderer
App->>Provider: Bootstrap with appConfig
Provider->>Provider: Create DEVTOOLS_OPTIONS_SIGNAL
Provider->>Provider: Initialize TANSTACK_DEVTOOLS_PLUGINS signal
Provider->>Core: Attach to ApplicationRef
Core->>Core: afterNextRender hook
Core->>Plugins: getPluginsMap(plugins)
loop For Each Plugin
Core->>Renderer: convertPlugin(pluginDef)
Renderer->>Renderer: Resolve render (sync/async)
Renderer->>Renderer: createComponent or functional render
Renderer->>Core: Return rendered component ref
Core->>Core: attachComponentRef (setup inputs/lifecycle)
Core->>Core: Track in componentRefs map
end
Core->>Core: Initialize TanStackDevtoolsCore
Core->>Core: Mount to host element
App->>EventBus: Emit 'counter-state' event
EventBus->>Plugins: Dispatch event to subscribers
Plugins->>Renderer: Update component inputs via effect
Renderer->>App: Reflect state changes in UI
App->>Plugins: Update plugins signal
Core->>Core: Watch for plugins change
Core->>Renderer: destroyComponentsInContainer(old)
Core->>Plugins: Re-render updated plugins
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
🟡 Minor comments (19)
examples/angular/with-devtools/src/index.html-5-5 (1)
5-5:⚠️ Potential issue | 🟡 MinorTitle does not match the example name.
The title is "Basic" but this example is "with-devtools". This appears to be a copy-paste artifact from another example.
📝 Suggested fix
- <title>Basic</title> + <title>With Devtools</title>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/with-devtools/src/index.html` at line 5, The page title in index.html is incorrect ("Basic"); update the <title> element in the file so it matches the example name ("with-devtools" or a human-friendly variant like "With Devtools") to avoid the copy-paste artifact and ensure the example metadata/title reflects this example.examples/angular/with-devtools/.editorconfig-11-17 (1)
11-17:⚠️ Potential issue | 🟡 MinorRemove the redundant IntelliJ-specific TypeScript setting.
The
ij_typescript_use_double_quotes = falsesetting at line 13 is redundant with the standardquote_type = singlesetting above it. Since this.editorconfigsetsroot = true, it's independent of the parent config and doesn't need to maintain consistency with it. However, the duplicate setting should be removed for clarity:Remove redundant IntelliJ setting
[*.ts] quote_type = single -ij_typescript_use_double_quotes = false🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/with-devtools/.editorconfig` around lines 11 - 17, Remove the redundant IntelliJ-specific TypeScript setting by deleting the line "ij_typescript_use_double_quotes = false" from the file; keep the standard "[*.ts]" block with "quote_type = single" as the single source of truth for quote style and remove only the "ij_typescript_use_double_quotes" entry so there is no duplicate configuration.examples/angular/panel/src/index.html-5-5 (1)
5-5:⚠️ Potential issue | 🟡 MinorUpdate the page title to match the panel example.
<title>Basic</title>looks like a copy/paste mismatch for this panel app and can be confusing in browser tabs.Suggested fix
- <title>Basic</title> + <title>Panel</title>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/panel/src/index.html` at line 5, Update the HTML title in examples/angular/panel/src/index.html: replace the current <title>Basic</title> with a more accurate title reflecting the app (for example "Angular Panel Example" or "Panel") so the browser tab and metadata correctly identify this panel demo; modify the <title> tag content only to match the panel example name.examples/angular/panel/.vscode/launch.json-9-16 (1)
9-16:⚠️ Potential issue | 🟡 Minor
preLaunchTaskreferences npm but project uses pnpm.The
preLaunchTaskvalues use"npm: start"and"npm: test", but thepackage.jsonspecifiespnpm@10.24.0as the package manager. VS Code may not recognize these tasks correctly. Consider updating to match the project's package manager or ensure VS Code's task auto-detection handles this.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/panel/.vscode/launch.json` around lines 9 - 16, Update the VS Code launch configuration so the preLaunchTask matches the project's package manager: replace the "preLaunchTask" values referencing "npm: start" and "npm: test" with the corresponding "pnpm: start" and "pnpm: test" (or ensure tasks with those names exist), targeting the entries that define the Chrome launches ("name": "ng serve"/implicit block and "name": "ng test") so VS Code will run the correct pnpm tasks before launching.examples/angular/with-devtools/angular.json-12-13 (1)
12-13:⚠️ Potential issue | 🟡 MinorProject name should match the example folder.
The project is named
"basic"but this configuration is in thewith-devtoolsexample directory. This appears to be a copy-paste oversight from another example. Consider renaming to"with-devtools"for consistency and to avoid confusion.📝 Proposed fix
"projects": { - "basic": { + "with-devtools": { "projectType": "application",Also update build targets on lines 61 and 64:
- "buildTarget": "basic:build:production" + "buildTarget": "with-devtools:build:production" }, "development": { - "buildTarget": "basic:build:development" + "buildTarget": "with-devtools:build:development"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/with-devtools/angular.json` around lines 12 - 13, The project key in the angular.json "projects" object is incorrectly named "basic" but should match the example folder; rename the project key from "basic" to "with-devtools" and update every reference to that key inside the file (notably the build and serve targets currently referencing "basic") so they point to "with-devtools" instead; ensure target entries such as the "build" and "serve" (and any "architect" or "targets" blocks) are updated to use the new project name so configuration and CLI commands remain consistent.examples/angular/panel/README.md-1-3 (1)
1-3:⚠️ Potential issue | 🟡 MinorUpdate README title to match the panel example directory.
The README title says "# Basic" but this file is in the
panelexample directory. The title should be "# Panel" for consistency with the directory name.📝 Proposed fix
-# Basic +# Panel🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/panel/README.md` around lines 1 - 3, The README title currently reads "Basic" but should match the example directory name; update the top-level heading in the README.md by replacing the heading text "Basic" with "Panel" so the file title reads "# Panel" for consistency with the panel example directory.docs/framework/angular/guides/custom-plugins.md-178-178 (1)
178-178:⚠️ Potential issue | 🟡 MinorMinor grammar: "built in" should be hyphenated.
📝 Suggested fix
-Both the `TanStackDevtools` and the TanStack `EventClient` come with built in debug mode which will log to the console the emitted event as well as the EventClient status. +Both the `TanStackDevtools` and the TanStack `EventClient` come with built-in debug mode which will log to the console the emitted event as well as the EventClient status.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/framework/angular/guides/custom-plugins.md` at line 178, The phrase "built in" in the sentence referencing TanStackDevtools and the TanStack EventClient should be hyphenated; update the sentence so it reads "built-in debug mode" where "built in" appears (affecting the line mentioning TanStackDevtools and EventClient) to correct the grammar.examples/angular/with-devtools/README.md-1-3 (1)
1-3:⚠️ Potential issue | 🟡 MinorREADME title mismatch with project directory.
The title says "Basic" but this file is in the
with-devtoolsexample directory. This appears to be a copy-paste oversight from the basic example.📝 Suggested fix
-# Basic +# With Devtools -This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 21.2.0. +This project was generated using [Angular CLI](https://github.com/angular/angular-cli).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/with-devtools/README.md` around lines 1 - 3, The README title currently reads "Basic" which doesn't match this example's directory; update the top-level heading in examples/angular/with-devtools/README.md (the line containing "# Basic") to a correct, descriptive title such as "# with-devtools" or "# Angular with DevTools" so the file name and content reflect the example's purpose.docs/framework/angular/guides/custom-plugins.md-197-197 (1)
197-197:⚠️ Potential issue | 🟡 MinorMinor spelling: "Where as" should be "Whereas".
📝 Suggested fix
-Where as the EventClient's debug mode can be activated by: +Whereas the EventClient's debug mode can be activated by:🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/framework/angular/guides/custom-plugins.md` at line 197, Replace the incorrect two-word phrase "Where as" with the correct single-word "Whereas" in the docs/framework/angular/guides/custom-plugins.md content to fix the minor spelling error.docs/framework/angular/guides/custom-plugins.md-213-216 (1)
213-216:⚠️ Potential issue | 🟡 MinorAdd language specifier to fenced code block.
The code block showing debug output should have a language specifier for consistent formatting.
📝 Suggested fix
-``` +```text [tanstack-devtools:client-bus] Initializing client event bus [tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state ```🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/framework/angular/guides/custom-plugins.md` around lines 213 - 216, The fenced code block showing debug output lacks a language specifier; update the block in docs/framework/angular/guides/custom-plugins.md around the debug output containing "[tanstack-devtools:client-bus] Initializing client event bus" and "[tanstack-devtools:custom-devtools-plugin] Registered event to bus custom-devtools:counter-state" by adding a language tag (e.g., ```text) after the opening backticks so the block becomes a fenced code block with the "text" specifier for consistent formatting.docs/framework/angular/guides/custom-plugins.md-122-135 (1)
122-135:⚠️ Potential issue | 🟡 MinorTemplate incorrectly calls getter with parentheses.
In the template at line 125,
stateHistory()is called with parentheses, butstateHistoryis defined as a getter (line 133-135), not a method. Getters should be accessed without parentheses.📝 Suggested fix
template: ` <div> <div>{{ state()?.count }}</div> - <div>{{ stateHistory() }}</div> + <div>{{ stateHistory }}</div> </div> `,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/framework/angular/guides/custom-plugins.md` around lines 122 - 135, The template incorrectly invokes the getter stateHistory as a method; update the template to reference the getter without parentheses (use {{ stateHistory }} instead of {{ stateHistory() }}) so it matches the DevtoolPanel get stateHistory() accessor and returns the computed JSON string properly.docs/framework/angular/basic-setup.md-18-32 (1)
18-32:⚠️ Potential issue | 🟡 MinorFix import name mismatch in code example.
The import statement on line 20 imports
TanStackDevtoolsComponent, but theimportsarray on line 25 referencesTanStackDevtools. This inconsistency will cause a compilation error when users follow this documentation.📝 Proposed fix
```typescript import { Component } from '@angular/core' -import { TanStackDevtoolsComponent } from '@tanstack/angular-devtools' +import { TanStackDevtools } from '@tanstack/angular-devtools' `@Component`({ selector: 'app-root', standalone: true, imports: [TanStackDevtools],🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/framework/angular/basic-setup.md` around lines 18 - 32, The import name in the example is inconsistent: the file imports TanStackDevtoolsComponent but the App component's standalone imports array references TanStackDevtools. Fix by making the imported symbol match the one used in the App component — either change the import to TanStackDevtools (recommended) or change the imports array to use TanStackDevtoolsComponent so the imported identifier and the imports entry are the same; update the import statement near the top that currently names TanStackDevtoolsComponent accordingly.docs/framework/angular/adapter.md-46-48 (1)
46-48:⚠️ Potential issue | 🟡 MinorMisleading text references non-existent
componentfield.Line 48 states the adapter "uses
component... instead ofrender", but the actual field name shown in the type definition and code examples isrender. This appears to be outdated or incorrect.📝 Proposed fix
-The Angular adapter uses `component` (an Angular component class reference) instead of `render` (a JSX element) in plugin definitions. Inputs are provided through the `inputs` field and bound to the component with `setInput()`, rather than being embedded directly in a JSX expression or passed via `v-bind`. +The Angular adapter uses `render` with an Angular component class reference (rather than a JSX element) in plugin definitions. Inputs are provided through the `inputs` field and bound to the component with `setInput()`, rather than being embedded directly in a JSX expression or passed via `v-bind`.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/framework/angular/adapter.md` around lines 46 - 48, The sentence incorrectly refers to a non-existent `component` field; update the documentation text to reference the actual `render` field used in the adapter type and examples (e.g., change "uses `component` (an Angular component class reference) instead of `render`" to indicate that the adapter uses `render` and explain that inputs are provided via the `inputs` field and bound with `setInput()`), or if the implementation should instead use `component`, update the type/examples to match—ensure consistency between the docs and the symbols `render`, `inputs`, and `setInput()`.docs/framework/angular/adapter.md-38-45 (1)
38-45:⚠️ Potential issue | 🟡 MinorDocumentation table has incorrect type for
renderfield.The table states
renderhas typeType<any>, but the type definition above (line 31) shows it should beTanStackDevtoolsAngularPluginRender. The description should align with the actual type definition for accuracy.📝 Proposed fix
-| `render` | `Type<any>` | The Angular component class to render as the plugin panel content. | +| `render` | `TanStackDevtoolsAngularPluginRender` | The Angular component class, lazy component (via dynamic import), or custom function to render as the plugin panel content. |🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/framework/angular/adapter.md` around lines 38 - 45, The docs table incorrectly types the render field as Type<any>; update the table entry for the `render` row to use the actual type name `TanStackDevtoolsAngularPluginRender` and adjust the description to reference that type (e.g., "The render property is a TanStackDevtoolsAngularPluginRender that defines the Angular component/class used for plugin panel content"); ensure the `render` column now matches the type alias `TanStackDevtoolsAngularPluginRender` used in the code.packages/angular-devtools/src/types.ts-44-62 (1)
44-62:⚠️ Potential issue | 🟡 MinorJSDoc example references
componentbut type usesrender.The JSDoc comment states "Each plugin should have a
component" and the example showscomponent: MyPluginComponent, but the actualTanStackDevtoolsAngularPlugintype at line 38 definesrender, notcomponent. This mismatch will confuse users.📝 Proposed fix to align JSDoc with actual type
/** * Array of plugins to be used in the devtools. - * Each plugin should have a `component` that is an Angular component class. + * Each plugin should have a `render` property that specifies an Angular component class or render function. * * Example: * ```typescript * `@Component`({ * template: `<tanstack-devtools [plugins]="plugins" />`, * imports: [TanStackDevtoolsComponent], * }) * class AppComponent { * plugins: TanStackDevtoolsAngularPlugin[] = [ - * { name: 'My Plugin', component: MyPluginComponent } + * { name: 'My Plugin', render: MyPluginComponent } * ]; * } * ``` */🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/angular-devtools/src/types.ts` around lines 44 - 62, The JSDoc for TanStackDevtoolsAngularInit and its example incorrectly refer to a `component` property while the actual type TanStackDevtoolsAngularPlugin defines `render`; update the JSDoc/example to use `render` (e.g., replace `{ name: 'My Plugin', component: MyPluginComponent }` with `{ name: 'My Plugin', render: MyPluginComponent }`) so the documentation matches the TanStackDevtoolsAngularPlugin type and avoids confusion.packages/devtools-utils/src/angular/panel.ts-53-57 (1)
53-57:⚠️ Potential issue | 🟡 MinorAdd error handling for the lazy-loaded panel.
When
CoreClassis a function returning a Promise (lazy loading), there's no.catch()handler. If the dynamic import fails, the error will be unhandled.Proposed fix
if (isConstructor) { mount(new CoreClass(inputs())) } else { CoreClass() .then((ResolvedCoreClass) => new ResolvedCoreClass(inputs())) .then(mount) + .catch((error) => { + console.error('[createAngularPanel] Failed to load panel:', error) + }) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-utils/src/angular/panel.ts` around lines 53 - 57, The lazy-load branch calling CoreClass() lacks error handling; wrap the promise chain starting at CoreClass() so rejections are caught and handled—add a .catch handler to the CoreClass().then(...).then(mount) chain that logs the error and/or invokes the existing mount/error handling flow (reference CoreClass, inputs(), and mount) to avoid unhandled promise rejections and surface failures during dynamic import.packages/angular-devtools/provider/with-devtools.ts-75-80 (1)
75-80:⚠️ Potential issue | 🟡 MinorUse
isPlatformBrowser()instead of string comparison for platform check.The
PLATFORM_IDvalue should be checked using Angular'sisPlatformBrowser()utility from@angular/commonrather than direct string comparison. The string'browser'is not the documented API contract.Proposed fix
Add to imports:
import { isPlatformBrowser } from '@angular/common'- if (inject(PLATFORM_ID) !== 'browser') { + if (!isPlatformBrowser(inject(PLATFORM_ID))) { return }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/angular-devtools/provider/with-devtools.ts` around lines 75 - 80, Replace the direct string comparison of PLATFORM_ID with Angular's isPlatformBrowser check: import isPlatformBrowser from '@angular/common' and change the platform guard in the provideEnvironmentInitializer block (where inject(PLATFORM_ID) is used) to use if (!isPlatformBrowser(inject(PLATFORM_ID))) return; so the environment check uses the documented API rather than comparing to the string 'browser'.packages/angular-devtools/provider/with-devtools.ts-91-103 (1)
91-103:⚠️ Potential issue | 🟡 MinorAdd error handling for the dynamic import.
The dynamic import of
@tanstack/angular-devtoolshas no.catch()handler. If the import fails (e.g., network error, bundler misconfiguration), the error will be silently swallowed or result in an unhandled promise rejection.Proposed fix
import('@tanstack/angular-devtools').then(({ TanStackDevtools }) => { const componentRef = createComponent(TanStackDevtools, { environmentInjector: injector, bindings: [ inputBinding('config', config), inputBinding('plugins', plugins), inputBinding('eventBusConfig', eventBusConfig), ], }) applicationRef.attachView(componentRef.hostView) componentRef.changeDetectorRef.detectChanges() - }) + }).catch((error) => { + console.error('[`@tanstack/angular-devtools`] Failed to load devtools:', error) + })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/angular-devtools/provider/with-devtools.ts` around lines 91 - 103, The dynamic import of '@tanstack/angular-devtools' in with-devtools.ts lacks error handling; update the import promise chain that calls import('@tanstack/angular-devtools').then(...) to add a .catch handler that logs the error and a contextual message (e.g., "Failed to load TanStackDevtools") and avoids leaving an unhandled rejection; reference the import call and the createComponent/TanStackDevtools block so the catch can report the error and optionally perform a safe fallback (e.g., skip attaching the devtools) if the module fails to load.packages/devtools-utils/src/angular/panel.ts-15-20 (1)
15-20:⚠️ Potential issue | 🟡 MinorType guard using
!!o.prototypeis fragile.This check relies on the fact that arrow functions and async functions lack a
prototypeproperty, while class constructors have one. However, regular functions also haveprototype, so if someone passes a non-arrow function that returns a Promise, it would incorrectly be identified as a constructor.Consider a more robust check:
Proposed fix
function isPanelClassConstructor< TComponentProps extends DevtoolsPanelProps, TCoreDevtoolsClass extends BaseCorePanelClass, >(o: any): o is CoreClassConstructor<TComponentProps, TCoreDevtoolsClass> { - return !!o.prototype + // Classes have a prototype with a constructor property pointing back to themselves + return typeof o === 'function' && o.prototype?.constructor === o && !o.prototype[Symbol.toStringTag] }Alternatively, if the lazy factory is always expected to be an arrow function or async function (which is typical for dynamic imports), the current approach works in practice but deserves a comment explaining the assumption.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-utils/src/angular/panel.ts` around lines 15 - 20, The type-guard isPanelClassConstructor currently uses !!o.prototype which misclassifies plain functions as class constructors; replace that heuristic with a robust check that ensures o is an ES6 class constructor, e.g. verify typeof o === 'function' and use Function.prototype.toString.call(o) to test for a leading "class" token (e.g. /^class\s/.test(...)); update the function isPanelClassConstructor<TComponentProps, TCoreDevtoolsClass> to use this test (or, if you intentionally only accept arrow/async factories, add a clear comment explaining that assumption) so non-class functions are not treated as constructors.
🧹 Nitpick comments (14)
examples/angular/with-devtools/.vscode/mcp.json (1)
6-6: Angular's official MCP documentation uses unpinned@angular/cliintentionally.The unpinned version on line 6 aligns with Angular's documented setup for
.vscode/mcp.json. Since the MCP server is experimental, Angular recommends tracking the latest CLI to pick up fixes and improvements rather than pinning.While pinning to
@angular/cli@20would improve reproducibility across team members, it goes against Angular's official guidance for this feature. If your team prefers consistency and controlled upgrades, pinning is a valid choice, but it's not the documented approach.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/with-devtools/.vscode/mcp.json` at line 6, The current args entry ["-y", "@angular/cli", "mcp"] intentionally uses an unpinned `@angular/cli` per Angular's MCP docs—leave it as-is to track latest fixes; if you instead want reproducible behavior across the team, change that args element to "@angular/cli@20" (i.e., replace "@angular/cli" with "@angular/cli@20") so the MCP launch uses the pinned CLI version.examples/angular/panel/package.json (1)
22-22: Inconsistent version specifier for@tanstack/devtools-event-client.This dependency uses an exact version (
0.4.2) while other@tanstack/*packages use caret ranges (^0.0.1,^0.10.13,^0.3.3). Consider using a caret range for consistency unless pinning is intentional.Suggested fix
- "@tanstack/devtools-event-client": "0.4.2", + "@tanstack/devtools-event-client": "^0.4.2",🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/panel/package.json` at line 22, The dependency "@tanstack/devtools-event-client" is pinned to an exact version "0.4.2" while other `@tanstack` packages in package.json use caret ranges; update the version specifier for "@tanstack/devtools-event-client" to use a caret range (e.g. "^0.4.2") to match the other `@tanstack/`* entries and maintain consistent semver behavior across the project.packages/devtools-utils/vite.config.angular.ts (1)
5-7: Drop the emptypluginsfield for clarity.No behavior change, just removes noise in the config object.
♻️ Suggested cleanup
const config = defineConfig({ - plugins: [], test: { name: packageJson.name,🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-utils/vite.config.angular.ts` around lines 5 - 7, The config object created via defineConfig currently includes an unnecessary empty "plugins" property; remove the "plugins: []" entry from the defineConfig call (the config constant) to clean up the Vite configuration without changing behavior—locate the defineConfig({ ... }) block that assigns to config and delete the plugins key.examples/angular/basic/src/app/devtools/custom-devtools-panel.ts (1)
27-28: Clarify the "Automatically added" comment.The comment
// Automatically addedon thethemeinput is unclear. Consider elaborating on what "automatically adds" this input or remove the comment if it's no longer relevant.📝 Suggested clarification
- // Automatically added + // Injected by the devtools plugin framework readonly theme = input<string>()🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/basic/src/app/devtools/custom-devtools-panel.ts` around lines 27 - 28, The "// Automatically added" comment above the readonly theme = input<string>() declaration is ambiguous; either replace it with a clearer note that states who/what adds this input and when (e.g., "Added by DevTools host at runtime" or "Injected by framework build step") or remove the comment entirely if it's stale; update the comment adjacent to the readonly theme = input<string>() symbol to explicitly state its source and purpose so future readers understand why the input exists.examples/angular/panel/src/app/counter.ts (2)
10-28: Consider extracting duplicated emit logic.The
DevtoolsEventClient.emit('counter-state', ...)call is duplicated in bothincrementanddecrement. Consider extracting this into a helper function for DRY code.♻️ Proposed refactor
export function createCounter() { const count = signal(0) const history: Array<number> = [] + const emitState = () => { + DevtoolsEventClient.emit('counter-state', { + count: count(), + history, + }) + } + return { value: () => count(), increment: () => { count.update((n) => n + 1) history.push(count()) - - // The emit eventSuffix must match that of the EventMap defined in eventClient.ts - DevtoolsEventClient.emit('counter-state', { - count: count(), - history, - }) + emitState() }, decrement: () => { count.update((n) => n - 1) history.push(count()) - - DevtoolsEventClient.emit('counter-state', { - count: count(), - history, - }) + emitState() }, } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/panel/src/app/counter.ts` around lines 10 - 28, increment and decrement both call DevtoolsEventClient.emit('counter-state', { count: count(), history }) causing duplicated emit logic; extract that into a small helper (e.g., emitCounterState or notifyCounterState) that reads count() and history and calls DevtoolsEventClient.emit('counter-state', payload), then replace the duplicated emit calls in the increment and decrement functions with a single call to the new helper; ensure the helper is colocated with the count/history variables and used after updating history so payload stays correct.
4-9: Consider emitting initial state.The panel won't display any state until the user clicks increment or decrement for the first time. Consider emitting the initial state when the counter is created, or document this behavior as intentional.
💡 Optional: Emit initial state
export function createCounter() { const count = signal(0) const history: Array<number> = [] + + // Emit initial state + DevtoolsEventClient.emit('counter-state', { + count: count(), + history, + }) return {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/panel/src/app/counter.ts` around lines 4 - 9, createCounter currently never emits its initial value so the UI sees no state until the first increment/decrement; fix it by emitting the initial state when the counter is created: after initializing count and history in createCounter, record the initial value (e.g., push count() into history) and invoke the same notifier/emit function you use for subsequent updates (or expose/ call value() once) so the panel receives the initial state; update references in createCounter, count, history and the notifier/emit handler accordingly.docs/framework/angular/basic-setup.md (1)
51-58: Class name inconsistency between examples.The first example uses
Appas the class name (line 31), while this example usesAppComponent(line 51). For consistency within the same documentation page, consider using the same naming convention.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/framework/angular/basic-setup.md` around lines 51 - 58, The examples use inconsistent class names — one declares class App and the later snippet declares class AppComponent; update the later example so the class name matches the first example (e.g., rename AppComponent to App) and ensure the export and any internal references (plugins array, TanStackDevtoolsAngularPlugin, AngularQueryDevtoolsPanel) are updated accordingly so both examples use the same class identifier.packages/angular-devtools/package.json (2)
55-63: Duplicate tslib in dependencies and devDependencies.
tslibis listed in bothdependencies(line 57) anddevDependencies(line 62). The runtime dependency listing is sufficient; the devDependencies entry is redundant.♻️ Remove duplicate tslib
"devDependencies": { "@angular/core": "^21.2.0", - "ng-packagr": "^21.2.0", - "tslib": "^2.3.0" + "ng-packagr": "^21.2.0" },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/angular-devtools/package.json` around lines 55 - 63, Remove the duplicate tslib entry by keeping it under "dependencies" and deleting the "tslib" entry from "devDependencies" in package.json; update the package.json object so only the top-level "dependencies" contains "tslib" and ensure no other references to tslib remain in dev tooling config (look for "tslib" in the package.json dependencies/devDependencies blocks).
53-53: Build script uses Unix-specific commands.The
rm -rfcommands in the build script are not cross-platform. This may cause issues for contributors on Windows.♻️ Use cross-platform alternative
Consider using
rimrafor the existingpremove(used in the clean script) for cross-platform compatibility:- "build": "ng-packagr -p ng-package.json -c tsconfig.build.json && rm -rf ./dist/package.json && rm -rf ./dist/provider/package.json" + "build": "ng-packagr -p ng-package.json -c tsconfig.build.json && premove ./dist/package.json ./dist/provider/package.json"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/angular-devtools/package.json` at line 53, The build script in package.json ("build") uses Unix-only rm -rf; replace those calls with a cross-platform remover such as rimraf (or reuse the existing "premove" helper used by "clean") so Windows contributors won't fail; update the "build" script to call rimraf on ./dist/package.json and ./dist/provider/package.json (or call the existing preremove command), and if using rimraf add it to devDependencies and/or the scripts so CI and local installs have the tool available.examples/angular/basic/src/app/app.ts (1)
19-21: Consider using a more meaningful defer trigger condition.
@defer (when true)is equivalent to an unconditional defer, which loads the content when the browser is idle. This works but thewhen truecondition is redundant and potentially confusing.♻️ Optional: Simplified defer syntax
- `@defer` (when true) { + `@defer` { <tanstack-devtools [plugins]="plugins()" [eventBusConfig]="{ debug: true }" /> }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/basic/src/app/app.ts` around lines 19 - 21, The `@defer` block uses an explicit always-true condition (`@defer` (when true)) which is redundant; change it to use the simpler unconditional defer syntax or a meaningful predicate instead. Update the `@defer` usage wrapping <tanstack-devtools [plugins]="plugins()" [eventBusConfig]="{ debug: true }" /> by removing "(when true)" or replacing it with a real trigger (e.g., a boolean like isIdle or isDevMode) so the intent is clear and the defer behavior for tanstack-devtools and plugins() is preserved.examples/angular/panel/src/app/devtools/custom-devtools-panel.ts (1)
12-17: Consider using textContent for safer DOM updates (optional for example code).The static analysis flagged the
innerHTMLusage. While in this example the data is trusted internal state (numeric count and array history), for production patterns consider using DOM APIs liketextContentor a sanitization library.For this example demonstrating devtools integration, the current approach is acceptable.
♻️ Optional: Safer DOM update pattern
- function render(count: number, history: string) { - plugin.innerHTML = ` - <div>counter state: ${count}</div> - <div>counter history: ${history}</div> - ` - } + function render(count: number, history: string) { + plugin.textContent = '' + const countDiv = element.ownerDocument.createElement('div') + countDiv.textContent = `counter state: ${count}` + const historyDiv = element.ownerDocument.createElement('div') + historyDiv.textContent = `counter history: ${history}` + plugin.appendChild(countDiv) + plugin.appendChild(historyDiv) + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@examples/angular/panel/src/app/devtools/custom-devtools-panel.ts` around lines 12 - 17, The render(count: number, history: string) function writes unescaped HTML into plugin via plugin.innerHTML which static analysis flagged; change render to avoid innerHTML by creating or selecting child elements under the plugin container and setting their textContent (or concatenating safe strings) to display count and history, e.g., create two divs or reuse existing elements and assign their textContent using the plugin reference instead of assigning plugin.innerHTML.packages/angular-devtools/provider/with-devtools.ts (2)
86-89: Plugins are set once and won't react to changes indevtoolsOptions().plugins.
configandeventBusConfigare wrapped incomputed()to stay reactive, butpluginsis set imperatively withplugins.set(...)only during initialization. IfdevtoolsOptions().pluginschanges later, the devtools won't reflect those changes.If this is intentional (plugins are expected to be static), consider adding a brief comment. Otherwise, consider using an
effect()to keep plugins in sync:Proposed fix if reactivity is needed
+ import { effect } from '@angular/core' // ... - plugins.set(devtoolsOptions().plugins ?? []) + effect(() => { + plugins.set(devtoolsOptions().plugins ?? []) + })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/angular-devtools/provider/with-devtools.ts` around lines 86 - 89, The plugins array is only set once with plugins.set(devtoolsOptions().plugins ?? []) and won't update when devtoolsOptions().plugins changes; change this to a reactive effect that watches devtoolsOptions().plugins and calls plugins.set(...) whenever it updates (or add a comment if immutability is intentional). Specifically, replace the one-time call to plugins.set with an effect/autorun that reads devtoolsOptions().plugins and updates plugins via plugins.set(...) so it stays in sync with the computed config/eventBusConfig used elsewhere.
32-34: Consider tightening theArray<any>types.Both
WithDevtoolsOptionsFnandWithDevtoolsOptions.depsuseArray<any>, which loses type safety. If stronger typing isn't feasible due to Angular DI constraints, this is acceptable, but a brief JSDoc comment explaining whyanyis necessary would help future maintainers.Also applies to: 56-58
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/angular-devtools/provider/with-devtools.ts` around lines 32 - 34, Tighten the loose Array<any> usages by replacing them with a safer type (prefer unknown[] or make WithDevtoolsOptionsFn generic like <Args extends any[]>(...args: Args) => TanStackDevtoolsAllOptions and use Args for WithDevtoolsOptions.deps) so callers retain type information; if Angular DI constraints prevent stronger typing, add a short JSDoc above WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps field explaining why any is necessary and referencing the DI limitation. Ensure you update the declarations for WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps usage (the symbols named WithDevtoolsOptionsFn and WithDevtoolsOptions.deps) consistently.packages/devtools-utils/src/angular/panel.ts (1)
22-65: Consider adding JSDoc and explicit return type for public API clarity.
createAngularPanelis a public API but lacks documentation explaining its purpose, the tuple return structure, and why the second element is() => null. Adding a brief JSDoc (without@params/@returns per coding guidelines) and an explicit return type would improve discoverability.Example documentation
+/** + * Creates an Angular panel adapter for TanStack Devtools. + * + * Accepts either a CoreClass constructor directly or a lazy loader function + * that returns a Promise resolving to the constructor. + * + * Returns a tuple where the first element is a factory for mounting the panel, + * and the second is a no-op placeholder for framework compatibility. + */ export function createAngularPanel< TComponentProps extends DevtoolsPanelProps, TCoreDevtoolsClass extends BaseCorePanelClass, >( CoreClass: | CoreClassConstructor<TComponentProps, TCoreDevtoolsClass> | (() => Promise< CoreClassConstructor<TComponentProps, TCoreDevtoolsClass> >), -) { +): readonly [ + () => (inputs: () => TComponentProps, host: HTMLElement) => () => void, + () => null, +] {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/devtools-utils/src/angular/panel.ts` around lines 22 - 65, Add JSDoc and an explicit return type to the public function createAngularPanel to document its purpose, the tuple structure, and why the second tuple entry is () => null; update the function signature to include a clear return type (e.g., readonly [ (inputs: () => TComponentProps, host: HTMLElement) => () => void, () => null ]) and add a brief JSDoc block above createAngularPanel describing what the factory returns, what the first tuple function does (mount/unmount via CoreClass), and why the second is a stub; reference the createAngularPanel function, the returned tuple and the second element (() => null) when making these edits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@examples/angular/with-devtools/src/app/devtools/custom-devtools-panel.ts`:
- Around line 13-23: The `@Component` metadata for the custom devtools panel
includes an imports array (JsonPipe) but is missing standalone: true, which is
required for using imports; update the Component decorator metadata (the
`@Component` applied to the custom devtool panel) to include standalone: true so
the imports property is valid and the Angular compiler will succeed.
In `@packages/angular-devtools/src/devtools.ts`:
- Around line 275-279: The current isClassConstructor wrongly treats plain
functions as classes; replace its heuristic with a reliable ES6-class check: if
o is a function, call Function.prototype.toString on it and test for a leading
"class" token (e.g. /^\s*class\b/), returning true only for that case. Update
the isClassConstructor<T> implementation (the function named isClassConstructor)
to use this toString/regex check so functional renderers are not misclassified
and therefore not handed to renderComponent().
- Around line 125-151: The spread usage ...(plugin.inputs ?? {}) drops
non-plain-object inputs (functions/signals); instead stop spreading and pass the
inputs value directly as a property so functions/signals are preserved. Update
the three call sites that build the options object — the calls to
this.renderComponent(plugin.name...), this.renderComponent(plugin.render...),
and this.#renderComponentFunction(...) — to construct options like { theme,
inputs: plugin.inputs ?? {} } (or { theme, inputs: plugin.inputs } if a default
empty object isn't desired) rather than spreading plugin.inputs.
- Around line 142-153: The async call to this.#renderComponentFunction with
plugin.render (typed as TanStackDevtoolsAngularPluginRender) can reject and
currently has no error handling; wrap the call in a try/catch (or attach a
.catch) to handle rejections, log the error (include plugin identity/context),
and optionally fall back or swallow to prevent unhandled promise
rejections—update the call site where this.#renderComponentFunction(...) is
invoked to catch and handle any thrown errors.
---
Minor comments:
In `@docs/framework/angular/adapter.md`:
- Around line 46-48: The sentence incorrectly refers to a non-existent
`component` field; update the documentation text to reference the actual
`render` field used in the adapter type and examples (e.g., change "uses
`component` (an Angular component class reference) instead of `render`" to
indicate that the adapter uses `render` and explain that inputs are provided via
the `inputs` field and bound with `setInput()`), or if the implementation should
instead use `component`, update the type/examples to match—ensure consistency
between the docs and the symbols `render`, `inputs`, and `setInput()`.
- Around line 38-45: The docs table incorrectly types the render field as
Type<any>; update the table entry for the `render` row to use the actual type
name `TanStackDevtoolsAngularPluginRender` and adjust the description to
reference that type (e.g., "The render property is a
TanStackDevtoolsAngularPluginRender that defines the Angular component/class
used for plugin panel content"); ensure the `render` column now matches the type
alias `TanStackDevtoolsAngularPluginRender` used in the code.
In `@docs/framework/angular/basic-setup.md`:
- Around line 18-32: The import name in the example is inconsistent: the file
imports TanStackDevtoolsComponent but the App component's standalone imports
array references TanStackDevtools. Fix by making the imported symbol match the
one used in the App component — either change the import to TanStackDevtools
(recommended) or change the imports array to use TanStackDevtoolsComponent so
the imported identifier and the imports entry are the same; update the import
statement near the top that currently names TanStackDevtoolsComponent
accordingly.
In `@docs/framework/angular/guides/custom-plugins.md`:
- Line 178: The phrase "built in" in the sentence referencing TanStackDevtools
and the TanStack EventClient should be hyphenated; update the sentence so it
reads "built-in debug mode" where "built in" appears (affecting the line
mentioning TanStackDevtools and EventClient) to correct the grammar.
- Line 197: Replace the incorrect two-word phrase "Where as" with the correct
single-word "Whereas" in the docs/framework/angular/guides/custom-plugins.md
content to fix the minor spelling error.
- Around line 213-216: The fenced code block showing debug output lacks a
language specifier; update the block in
docs/framework/angular/guides/custom-plugins.md around the debug output
containing "[tanstack-devtools:client-bus] Initializing client event bus" and
"[tanstack-devtools:custom-devtools-plugin] Registered event to bus
custom-devtools:counter-state" by adding a language tag (e.g., ```text) after
the opening backticks so the block becomes a fenced code block with the "text"
specifier for consistent formatting.
- Around line 122-135: The template incorrectly invokes the getter stateHistory
as a method; update the template to reference the getter without parentheses
(use {{ stateHistory }} instead of {{ stateHistory() }}) so it matches the
DevtoolPanel get stateHistory() accessor and returns the computed JSON string
properly.
In `@examples/angular/panel/.vscode/launch.json`:
- Around line 9-16: Update the VS Code launch configuration so the preLaunchTask
matches the project's package manager: replace the "preLaunchTask" values
referencing "npm: start" and "npm: test" with the corresponding "pnpm: start"
and "pnpm: test" (or ensure tasks with those names exist), targeting the entries
that define the Chrome launches ("name": "ng serve"/implicit block and "name":
"ng test") so VS Code will run the correct pnpm tasks before launching.
In `@examples/angular/panel/README.md`:
- Around line 1-3: The README title currently reads "Basic" but should match the
example directory name; update the top-level heading in the README.md by
replacing the heading text "Basic" with "Panel" so the file title reads "#
Panel" for consistency with the panel example directory.
In `@examples/angular/panel/src/index.html`:
- Line 5: Update the HTML title in examples/angular/panel/src/index.html:
replace the current <title>Basic</title> with a more accurate title reflecting
the app (for example "Angular Panel Example" or "Panel") so the browser tab and
metadata correctly identify this panel demo; modify the <title> tag content only
to match the panel example name.
In `@examples/angular/with-devtools/.editorconfig`:
- Around line 11-17: Remove the redundant IntelliJ-specific TypeScript setting
by deleting the line "ij_typescript_use_double_quotes = false" from the file;
keep the standard "[*.ts]" block with "quote_type = single" as the single source
of truth for quote style and remove only the "ij_typescript_use_double_quotes"
entry so there is no duplicate configuration.
In `@examples/angular/with-devtools/angular.json`:
- Around line 12-13: The project key in the angular.json "projects" object is
incorrectly named "basic" but should match the example folder; rename the
project key from "basic" to "with-devtools" and update every reference to that
key inside the file (notably the build and serve targets currently referencing
"basic") so they point to "with-devtools" instead; ensure target entries such as
the "build" and "serve" (and any "architect" or "targets" blocks) are updated to
use the new project name so configuration and CLI commands remain consistent.
In `@examples/angular/with-devtools/README.md`:
- Around line 1-3: The README title currently reads "Basic" which doesn't match
this example's directory; update the top-level heading in
examples/angular/with-devtools/README.md (the line containing "# Basic") to a
correct, descriptive title such as "# with-devtools" or "# Angular with
DevTools" so the file name and content reflect the example's purpose.
In `@examples/angular/with-devtools/src/index.html`:
- Line 5: The page title in index.html is incorrect ("Basic"); update the
<title> element in the file so it matches the example name ("with-devtools" or a
human-friendly variant like "With Devtools") to avoid the copy-paste artifact
and ensure the example metadata/title reflects this example.
In `@packages/angular-devtools/provider/with-devtools.ts`:
- Around line 75-80: Replace the direct string comparison of PLATFORM_ID with
Angular's isPlatformBrowser check: import isPlatformBrowser from
'@angular/common' and change the platform guard in the
provideEnvironmentInitializer block (where inject(PLATFORM_ID) is used) to use
if (!isPlatformBrowser(inject(PLATFORM_ID))) return; so the environment check
uses the documented API rather than comparing to the string 'browser'.
- Around line 91-103: The dynamic import of '@tanstack/angular-devtools' in
with-devtools.ts lacks error handling; update the import promise chain that
calls import('@tanstack/angular-devtools').then(...) to add a .catch handler
that logs the error and a contextual message (e.g., "Failed to load
TanStackDevtools") and avoids leaving an unhandled rejection; reference the
import call and the createComponent/TanStackDevtools block so the catch can
report the error and optionally perform a safe fallback (e.g., skip attaching
the devtools) if the module fails to load.
In `@packages/angular-devtools/src/types.ts`:
- Around line 44-62: The JSDoc for TanStackDevtoolsAngularInit and its example
incorrectly refer to a `component` property while the actual type
TanStackDevtoolsAngularPlugin defines `render`; update the JSDoc/example to use
`render` (e.g., replace `{ name: 'My Plugin', component: MyPluginComponent }`
with `{ name: 'My Plugin', render: MyPluginComponent }`) so the documentation
matches the TanStackDevtoolsAngularPlugin type and avoids confusion.
In `@packages/devtools-utils/src/angular/panel.ts`:
- Around line 53-57: The lazy-load branch calling CoreClass() lacks error
handling; wrap the promise chain starting at CoreClass() so rejections are
caught and handled—add a .catch handler to the CoreClass().then(...).then(mount)
chain that logs the error and/or invokes the existing mount/error handling flow
(reference CoreClass, inputs(), and mount) to avoid unhandled promise rejections
and surface failures during dynamic import.
- Around line 15-20: The type-guard isPanelClassConstructor currently uses
!!o.prototype which misclassifies plain functions as class constructors; replace
that heuristic with a robust check that ensures o is an ES6 class constructor,
e.g. verify typeof o === 'function' and use Function.prototype.toString.call(o)
to test for a leading "class" token (e.g. /^class\s/.test(...)); update the
function isPanelClassConstructor<TComponentProps, TCoreDevtoolsClass> to use
this test (or, if you intentionally only accept arrow/async factories, add a
clear comment explaining that assumption) so non-class functions are not treated
as constructors.
---
Nitpick comments:
In `@docs/framework/angular/basic-setup.md`:
- Around line 51-58: The examples use inconsistent class names — one declares
class App and the later snippet declares class AppComponent; update the later
example so the class name matches the first example (e.g., rename AppComponent
to App) and ensure the export and any internal references (plugins array,
TanStackDevtoolsAngularPlugin, AngularQueryDevtoolsPanel) are updated
accordingly so both examples use the same class identifier.
In `@examples/angular/basic/src/app/app.ts`:
- Around line 19-21: The `@defer` block uses an explicit always-true condition
(`@defer` (when true)) which is redundant; change it to use the simpler
unconditional defer syntax or a meaningful predicate instead. Update the `@defer`
usage wrapping <tanstack-devtools [plugins]="plugins()" [eventBusConfig]="{
debug: true }" /> by removing "(when true)" or replacing it with a real trigger
(e.g., a boolean like isIdle or isDevMode) so the intent is clear and the defer
behavior for tanstack-devtools and plugins() is preserved.
In `@examples/angular/basic/src/app/devtools/custom-devtools-panel.ts`:
- Around line 27-28: The "// Automatically added" comment above the readonly
theme = input<string>() declaration is ambiguous; either replace it with a
clearer note that states who/what adds this input and when (e.g., "Added by
DevTools host at runtime" or "Injected by framework build step") or remove the
comment entirely if it's stale; update the comment adjacent to the readonly
theme = input<string>() symbol to explicitly state its source and purpose so
future readers understand why the input exists.
In `@examples/angular/panel/package.json`:
- Line 22: The dependency "@tanstack/devtools-event-client" is pinned to an
exact version "0.4.2" while other `@tanstack` packages in package.json use caret
ranges; update the version specifier for "@tanstack/devtools-event-client" to
use a caret range (e.g. "^0.4.2") to match the other `@tanstack/`* entries and
maintain consistent semver behavior across the project.
In `@examples/angular/panel/src/app/counter.ts`:
- Around line 10-28: increment and decrement both call
DevtoolsEventClient.emit('counter-state', { count: count(), history }) causing
duplicated emit logic; extract that into a small helper (e.g., emitCounterState
or notifyCounterState) that reads count() and history and calls
DevtoolsEventClient.emit('counter-state', payload), then replace the duplicated
emit calls in the increment and decrement functions with a single call to the
new helper; ensure the helper is colocated with the count/history variables and
used after updating history so payload stays correct.
- Around line 4-9: createCounter currently never emits its initial value so the
UI sees no state until the first increment/decrement; fix it by emitting the
initial state when the counter is created: after initializing count and history
in createCounter, record the initial value (e.g., push count() into history) and
invoke the same notifier/emit function you use for subsequent updates (or
expose/ call value() once) so the panel receives the initial state; update
references in createCounter, count, history and the notifier/emit handler
accordingly.
In `@examples/angular/panel/src/app/devtools/custom-devtools-panel.ts`:
- Around line 12-17: The render(count: number, history: string) function writes
unescaped HTML into plugin via plugin.innerHTML which static analysis flagged;
change render to avoid innerHTML by creating or selecting child elements under
the plugin container and setting their textContent (or concatenating safe
strings) to display count and history, e.g., create two divs or reuse existing
elements and assign their textContent using the plugin reference instead of
assigning plugin.innerHTML.
In `@examples/angular/with-devtools/.vscode/mcp.json`:
- Line 6: The current args entry ["-y", "@angular/cli", "mcp"] intentionally
uses an unpinned `@angular/cli` per Angular's MCP docs—leave it as-is to track
latest fixes; if you instead want reproducible behavior across the team, change
that args element to "@angular/cli@20" (i.e., replace "@angular/cli" with
"@angular/cli@20") so the MCP launch uses the pinned CLI version.
In `@packages/angular-devtools/package.json`:
- Around line 55-63: Remove the duplicate tslib entry by keeping it under
"dependencies" and deleting the "tslib" entry from "devDependencies" in
package.json; update the package.json object so only the top-level
"dependencies" contains "tslib" and ensure no other references to tslib remain
in dev tooling config (look for "tslib" in the package.json
dependencies/devDependencies blocks).
- Line 53: The build script in package.json ("build") uses Unix-only rm -rf;
replace those calls with a cross-platform remover such as rimraf (or reuse the
existing "premove" helper used by "clean") so Windows contributors won't fail;
update the "build" script to call rimraf on ./dist/package.json and
./dist/provider/package.json (or call the existing preremove command), and if
using rimraf add it to devDependencies and/or the scripts so CI and local
installs have the tool available.
In `@packages/angular-devtools/provider/with-devtools.ts`:
- Around line 86-89: The plugins array is only set once with
plugins.set(devtoolsOptions().plugins ?? []) and won't update when
devtoolsOptions().plugins changes; change this to a reactive effect that watches
devtoolsOptions().plugins and calls plugins.set(...) whenever it updates (or add
a comment if immutability is intentional). Specifically, replace the one-time
call to plugins.set with an effect/autorun that reads devtoolsOptions().plugins
and updates plugins via plugins.set(...) so it stays in sync with the computed
config/eventBusConfig used elsewhere.
- Around line 32-34: Tighten the loose Array<any> usages by replacing them with
a safer type (prefer unknown[] or make WithDevtoolsOptionsFn generic like <Args
extends any[]>(...args: Args) => TanStackDevtoolsAllOptions and use Args for
WithDevtoolsOptions.deps) so callers retain type information; if Angular DI
constraints prevent stronger typing, add a short JSDoc above
WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps field explaining why any
is necessary and referencing the DI limitation. Ensure you update the
declarations for WithDevtoolsOptionsFn and the WithDevtoolsOptions.deps usage
(the symbols named WithDevtoolsOptionsFn and WithDevtoolsOptions.deps)
consistently.
In `@packages/devtools-utils/src/angular/panel.ts`:
- Around line 22-65: Add JSDoc and an explicit return type to the public
function createAngularPanel to document its purpose, the tuple structure, and
why the second tuple entry is () => null; update the function signature to
include a clear return type (e.g., readonly [ (inputs: () => TComponentProps,
host: HTMLElement) => () => void, () => null ]) and add a brief JSDoc block
above createAngularPanel describing what the factory returns, what the first
tuple function does (mount/unmount via CoreClass), and why the second is a stub;
reference the createAngularPanel function, the returned tuple and the second
element (() => null) when making these edits.
In `@packages/devtools-utils/vite.config.angular.ts`:
- Around line 5-7: The config object created via defineConfig currently includes
an unnecessary empty "plugins" property; remove the "plugins: []" entry from the
defineConfig call (the config constant) to clean up the Vite configuration
without changing behavior—locate the defineConfig({ ... }) block that assigns to
config and delete the plugins key.
| @Component({ | ||
| selector: `custom-devtool-panel`, | ||
| template: ` | ||
| <div> | ||
| <div>counter state: {{ state()?.count }}</div> | ||
| <div>counter history: {{ state()?.history | json }}</div> | ||
| </div> | ||
| `, | ||
| changeDetection: ChangeDetectionStrategy.OnPush, | ||
| imports: [JsonPipe], | ||
| }) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify that the component metadata uses `imports` and whether `standalone: true` is present.
python - <<'PY'
from pathlib import Path
import re
path = Path("examples/angular/with-devtools/src/app/devtools/custom-devtools-panel.ts")
text = path.read_text()
match = re.search(r'@Component\(\{(?P<body>.*?)\}\)', text, re.S)
body = match.group('body') if match else ''
print("imports_present =", "imports:" in body)
print("standalone_true =", bool(re.search(r'standalone\s*:\s*true', body)))
PYRepository: TanStack/devtools
Length of output: 107
Add standalone: true to the component metadata.
Line 22 uses imports, which is only valid for standalone components. Without standalone: true, Angular compilation will fail.
Proposed fix
`@Component`({
selector: `custom-devtool-panel`,
+ standalone: true,
template: `
<div>
<div>counter state: {{ state()?.count }}</div>
<div>counter history: {{ state()?.history | json }}</div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [JsonPipe],
})📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @Component({ | |
| selector: `custom-devtool-panel`, | |
| template: ` | |
| <div> | |
| <div>counter state: {{ state()?.count }}</div> | |
| <div>counter history: {{ state()?.history | json }}</div> | |
| </div> | |
| `, | |
| changeDetection: ChangeDetectionStrategy.OnPush, | |
| imports: [JsonPipe], | |
| }) | |
| `@Component`({ | |
| selector: `custom-devtool-panel`, | |
| standalone: true, | |
| template: ` | |
| <div> | |
| <div>counter state: {{ state()?.count }}</div> | |
| <div>counter history: {{ state()?.history | json }}</div> | |
| </div> | |
| `, | |
| changeDetection: ChangeDetectionStrategy.OnPush, | |
| imports: [JsonPipe], | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@examples/angular/with-devtools/src/app/devtools/custom-devtools-panel.ts`
around lines 13 - 23, The `@Component` metadata for the custom devtools panel
includes an imports array (JsonPipe) but is missing standalone: true, which is
required for using imports; update the Component decorator metadata (the
`@Component` applied to the custom devtool panel) to include standalone: true so
the imports property is valid and the Angular compiler will succeed.
| this.renderComponent(plugin.name as Type<any>, e, { | ||
| theme, | ||
| ...(plugin.inputs ?? {}), | ||
| }) | ||
| }, | ||
| render: (e, theme) => { | ||
| if (!plugin.render) { | ||
| return | ||
| } | ||
|
|
||
| runInInjectionContext(this.#viewInjector, () => { | ||
| if (isClassConstructor<unknown>(plugin.render)) { | ||
| this.renderComponent(plugin.render, e, { | ||
| theme, | ||
| ...(plugin.inputs ?? {}), | ||
| }) | ||
| } else { | ||
| this.#renderComponentFunction( | ||
| plugin.render as Exclude< | ||
| TanStackDevtoolsAngularPluginRender, | ||
| Type<any> | null | ||
| >, | ||
| e, | ||
| { | ||
| theme, | ||
| ...(plugin.inputs ?? {}), | ||
| }, |
There was a problem hiding this comment.
Don’t spread plugin.inputs; it breaks function/signal inputs.
At Line 127/139/150, ...(plugin.inputs ?? {}) only works for plain objects. If plugin.inputs is a function/signal, inputs are dropped and reactivity is lost.
💡 Proposed fix
private convertPlugin(
plugin: TanStackDevtoolsAngularPlugin,
): TanStackDevtoolsPlugin {
+ const pluginInputs =
+ plugin.inputs ? this.#normalizeInputs(plugin.inputs as any) : () => ({})
+
return {
id: plugin.id,
defaultOpen: plugin.defaultOpen,
name:
typeof plugin.name === 'string'
? plugin.name
: (e, theme) => {
- this.renderComponent(plugin.name as Type<any>, e, {
- theme,
- ...(plugin.inputs ?? {}),
- })
+ this.renderComponent(plugin.name as Type<any>, e, () => ({
+ theme,
+ ...pluginInputs(),
+ }))
},
render: (e, theme) => {
if (!plugin.render) {
return
}
@@
if (isClassConstructor<unknown>(plugin.render)) {
- this.renderComponent(plugin.render, e, {
- theme,
- ...(plugin.inputs ?? {}),
- })
+ this.renderComponent(plugin.render, e, () => ({
+ theme,
+ ...pluginInputs(),
+ }))
} else {
this.#renderComponentFunction(
plugin.render as Exclude<
TanStackDevtoolsAngularPluginRender,
Type<any> | null
>,
e,
- {
- theme,
- ...(plugin.inputs ?? {}),
- },
+ () => ({
+ theme,
+ ...pluginInputs(),
+ }),
)
}
})
},🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/angular-devtools/src/devtools.ts` around lines 125 - 151, The spread
usage ...(plugin.inputs ?? {}) drops non-plain-object inputs
(functions/signals); instead stop spreading and pass the inputs value directly
as a property so functions/signals are preserved. Update the three call sites
that build the options object — the calls to
this.renderComponent(plugin.name...), this.renderComponent(plugin.render...),
and this.#renderComponentFunction(...) — to construct options like { theme,
inputs: plugin.inputs ?? {} } (or { theme, inputs: plugin.inputs } if a default
empty object isn't desired) rather than spreading plugin.inputs.
| this.#renderComponentFunction( | ||
| plugin.render as Exclude< | ||
| TanStackDevtoolsAngularPluginRender, | ||
| Type<any> | null | ||
| >, | ||
| e, | ||
| { | ||
| theme, | ||
| ...(plugin.inputs ?? {}), | ||
| }, | ||
| ) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's locate and examine the file
git ls-files "packages/angular-devtools/src/devtools.ts"Repository: TanStack/devtools
Length of output: 102
🏁 Script executed:
# Check file size before reading
wc -l packages/angular-devtools/src/devtools.tsRepository: TanStack/devtools
Length of output: 106
🏁 Script executed:
# Read the relevant section around lines 142-153
sed -n '130,160p' packages/angular-devtools/src/devtools.tsRepository: TanStack/devtools
Length of output: 892
🏁 Script executed:
# Find the `#renderComponentFunction` definition
rg "#renderComponentFunction" packages/angular-devtools/src/ -A 15 -B 2Repository: TanStack/devtools
Length of output: 2697
🏁 Script executed:
# Let's see more of the `#renderComponentFunction` implementation to understand it better
sed -n '260,279p' packages/angular-devtools/src/devtools.tsRepository: TanStack/devtools
Length of output: 484
🏁 Script executed:
# Check if there's any error handling at a higher level (in the render callback)
sed -n '120,170p' packages/angular-devtools/src/devtools.tsRepository: TanStack/devtools
Length of output: 1495
🏁 Script executed:
# Search for any try-catch or error handling patterns in the file
rg "catch|error|Error" packages/angular-devtools/src/devtools.ts -B 2 -A 2Repository: TanStack/devtools
Length of output: 255
🏁 Script executed:
# Let's see the full `#renderComponentFunction` implementation to understand what it does
sed -n '201,245p' packages/angular-devtools/src/devtools.tsRepository: TanStack/devtools
Length of output: 1369
🏁 Script executed:
# Check how renderComponent method handles errors (to compare with renderComponentFunction)
rg "renderComponent\(" packages/angular-devtools/src/devtools.ts -B 2 -A 10 | head -50Repository: TanStack/devtools
Length of output: 1249
🏁 Script executed:
# Check if there's any outer promise handling for the plugin system
rg "plugin\.render|render.*callback" packages/angular-devtools/src/ -B 3 -A 3Repository: TanStack/devtools
Length of output: 1430
🏁 Script executed:
# Let me see the full `#renderComponentFunction` to understand what can be thrown/rejected
sed -n '201,235p' packages/angular-devtools/src/devtools.tsRepository: TanStack/devtools
Length of output: 1077
🏁 Script executed:
# Check TypeScript config to understand strict mode settings
cat tsconfig.json 2>/dev/null | head -40Repository: TanStack/devtools
Length of output: 921
🏁 Script executed:
# Let me get the exact lines 142-153 with more context
sed -n '135,160p' packages/angular-devtools/src/devtools.ts | cat -nRepository: TanStack/devtools
Length of output: 986
🏁 Script executed:
# Verify the full async method to see what exceptions it can throw
sed -n '201,240p' packages/angular-devtools/src/devtools.ts | cat -nRepository: TanStack/devtools
Length of output: 1580
🏁 Script executed:
# Let me get the exact line numbers for the `#renderComponentFunction` definition
rg "async `#renderComponentFunction`" packages/angular-devtools/src/devtools.ts -nRepository: TanStack/devtools
Length of output: 98
🏁 Script executed:
# Check the complete `#renderComponentFunction` to see all possible error paths
sed -n '201,250p' packages/angular-devtools/src/devtools.ts | cat -nRepository: TanStack/devtools
Length of output: 1892
🏁 Script executed:
# Get the actual `#renderComponentFunction` implementation (line 170 onwards)
sed -n '170,200p' packages/angular-devtools/src/devtools.ts | cat -nRepository: TanStack/devtools
Length of output: 1356
🏁 Script executed:
# Verify the actual lines where the call happens (line 142 based on the review)
sed -n '138,155p' packages/angular-devtools/src/devtools.ts | cat -nRepository: TanStack/devtools
Length of output: 628
🏁 Script executed:
# Check if there's been any recent changes or different version of error handling
git log --oneline packages/angular-devtools/src/devtools.ts | head -5Repository: TanStack/devtools
Length of output: 137
Add error handling to async render call to prevent unhandled promise rejection.
The #renderComponentFunction method at line 142 is async and can reject when the lazy import or rendering fails, but the call site has no error handling. An unhandled rejection will bubble and can cause issues in strict environments.
Suggested fix
- this.#renderComponentFunction(
+ void this.#renderComponentFunction(
plugin.render as Exclude<
TanStackDevtoolsAngularPluginRender,
Type<any> | null
>,
e,
{
theme,
...(plugin.inputs ?? {}),
},
- )
+ ).catch((error) => {
+ console.error(
+ '[`@tanstack-devtools/angular`] Failed to render plugin',
+ error,
+ )
+ })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| this.#renderComponentFunction( | |
| plugin.render as Exclude< | |
| TanStackDevtoolsAngularPluginRender, | |
| Type<any> | null | |
| >, | |
| e, | |
| { | |
| theme, | |
| ...(plugin.inputs ?? {}), | |
| }, | |
| ) | |
| } | |
| void this.#renderComponentFunction( | |
| plugin.render as Exclude< | |
| TanStackDevtoolsAngularPluginRender, | |
| Type<any> | null | |
| >, | |
| e, | |
| { | |
| theme, | |
| ...(plugin.inputs ?? {}), | |
| }, | |
| ).catch((error) => { | |
| console.error( | |
| '[`@tanstack-devtools/angular`] Failed to render plugin', | |
| error, | |
| ) | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/angular-devtools/src/devtools.ts` around lines 142 - 153, The async
call to this.#renderComponentFunction with plugin.render (typed as
TanStackDevtoolsAngularPluginRender) can reject and currently has no error
handling; wrap the call in a try/catch (or attach a .catch) to handle
rejections, log the error (include plugin identity/context), and optionally fall
back or swallow to prevent unhandled promise rejections—update the call site
where this.#renderComponentFunction(...) is invoked to catch and handle any
thrown errors.
| function isClassConstructor<T>( | ||
| o: unknown, | ||
| ): o is new (...args: Array<any>) => T { | ||
| return typeof o === 'function' && !!o.prototype && !!o.prototype.constructor | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd packages/angular-devtools && head -300 src/devtools.ts | tail -50Repository: TanStack/devtools
Length of output: 1395
🏁 Script executed:
cat -n packages/angular-devtools/src/devtools.ts | sed -n '270,285p'Repository: TanStack/devtools
Length of output: 343
🏁 Script executed:
rg "isClassConstructor" packages/angular-devtools/Repository: TanStack/devtools
Length of output: 339
🏁 Script executed:
cat -n packages/angular-devtools/src/devtools.ts | sed -n '200,230p'Repository: TanStack/devtools
Length of output: 1296
🏁 Script executed:
rg -B 5 -A 5 "isClassConstructor" packages/angular-devtools/src/devtools.ts | head -30Repository: TanStack/devtools
Length of output: 874
🏁 Script executed:
python3 << 'EOF'
# Test both implementations to understand the issue
# Current implementation simulation
def current_check(obj):
"""Simulates: typeof o === 'function' && !!o.prototype && !!o.prototype.constructor"""
return callable(obj) and hasattr(obj, '__dict__') and hasattr(type(obj), '__init__')
# Proposed implementation would use regex on toString representation
def proposed_check(obj):
"""Simulates: /^class\s/.test(Function.prototype.toString.call(o))"""
try:
obj_str = str(obj)
return callable(obj) and obj_str.startswith('<built-in') == False
except:
return False
# Test cases
class MyClass:
pass
def my_function():
pass
def another_func():
return "test"
arrow_func = lambda x: x
# In JavaScript context, let's think about what the functions check:
# Current: typeof o === 'function' && !!o.prototype && !!o.prototype.constructor
# - In JS, both regular functions and classes have .prototype and .prototype.constructor
# - This means BOTH would return true, which is the bug
# Proposed: /^class\s/.test(Function.prototype.toString.call(o))
# - In JS, class toString() starts with "class"
# - Function toString() starts with "function"
# - This would correctly distinguish them
print("Testing JavaScript-like behavior:")
print("\nIn JavaScript:")
print("- Regular function has .prototype:", True)
print("- Regular function has .prototype.constructor:", True)
print("- Class has .prototype:", True)
print("- Class has .prototype.constructor:", True)
print("\nConclusion: Current check cannot distinguish regular functions from classes")
print("\nString representation:")
print("- Regular function toString(): 'function myFunc() { ... }'")
print("- Class toString(): 'class MyClass { ... }'")
print("\nConclusion: Proposed fix correctly distinguishes them via regex")
EOFRepository: TanStack/devtools
Length of output: 541
🏁 Script executed:
cat -n packages/angular-devtools/src/devtools.ts | sed -n '95,145p'Repository: TanStack/devtools
Length of output: 1800
isClassConstructor cannot distinguish regular functions from class constructors.
The current implementation checks for typeof o === 'function' && !!o.prototype && !!o.prototype.constructor, but both regular functions and class constructors have these properties in JavaScript. This causes functional renderers to be misclassified as component classes and routed to renderComponent() (line 136, 154), causing runtime failures.
Proposed fix
function isClassConstructor<T>(
o: unknown,
): o is new (...args: Array<any>) => T {
- return typeof o === 'function' && !!o.prototype && !!o.prototype.constructor
+ return (
+ typeof o === 'function' &&
+ /^class\s/.test(Function.prototype.toString.call(o))
+ )
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/angular-devtools/src/devtools.ts` around lines 275 - 279, The
current isClassConstructor wrongly treats plain functions as classes; replace
its heuristic with a reliable ES6-class check: if o is a function, call
Function.prototype.toString on it and test for a leading "class" token (e.g.
/^\s*class\b/), returning true only for that case. Update the
isClassConstructor<T> implementation (the function named isClassConstructor) to
use this toString/regex check so functional renderers are not misclassified and
therefore not handed to renderComponent().


The following PR continues the work of #364 to improve and fix angular integration:
Added some examples:
Summary by CodeRabbit
Release Notes
New Features
@tanstack/angular-devtoolspackageprovideTanStackDevtools()Documentation
Examples